﻿using System;
using System.Collections.Generic;
using System.Web;
using System.Text;
using System.Collections;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace ResourceBlender_Express {
    /// <summary>
    /// Miscellaneuous methods
    /// </summary>
    public delegate void ShowMessageDelegate(string message, string caption);
    public class Utils {
        /// <summary>
        /// Joins a list using a separator.
        /// </summary>
        /// <param name="list">The list to join.</param>
        /// <param name="separator">The separator to use.</param>
        /// <param name="trim">If set to <c>true</c>, trims whitespace from each index.</param>
        /// <returns></returns>
        public static string Implode(ICollection<string> list, string separator, bool trim) {
            StringBuilder buffer = new StringBuilder();
            int num = list.Count,
                added = 0;
            foreach(string item in list) {
                if(trim) {
                    buffer.Append(item.Trim());
                } else {
                    buffer.Append(item);
                }
                if(added + 1 < num) {
                    buffer.Append(separator);
                }
                added++;
            }
            return buffer.ToString();
        }

        /// <summary>
        /// Generates a (hopefully) unique ID using a combination of 53 (non-HTML safe) or 39 (HTML safe) characters.
        /// There are 1.74e17 combinations if the ID does not need to be HTML safe, otherwise there are just over 3500 million
        /// </summary>
        /// <param name="HTMLSafe"><c>true</c> if the generated ID must be valid in HTML without encoding.</param>
        /// <returns>The generated ID</returns>
        public static string GenID(bool HTMLSafe) {
            StringBuilder bld = new StringBuilder();
            int len = 6;
            Random seed = new Random();
            if(HTMLSafe) {
                string possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789~_-",
                       firstPossible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ"; // make sure the first character isn't a number, which invalidates xhtml
                bld.Append(possible[seed.Next(firstPossible.Length - 1)]);
                for(int i = 1; i < len; i++) {
                    bld.Append(possible[seed.Next(possible.Length - 1)]);
                }
            } else {
                string possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789%£$!*/#~][{}|=_-^";
                for(int i = 0; i < len; i++) {
                    bld.Append(possible[seed.Next(possible.Length - 1)]);
                }
            }
            return bld.ToString();
        }

        /// <summary>
        /// Calculates the MD5 hash of the specified text
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns>The MD5 hash.</returns>
        public static string MD5(string text) {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] originalBytes = ASCIIEncoding.UTF8.GetBytes(text);
            byte[] encodedBytes = md5.ComputeHash(originalBytes);
            return BitConverter.ToString(encodedBytes).Replace("-", "").ToLower();
        }

        /// <summary>
        /// Applies salt to a password.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        public static string SaltPassword(string password) {
            return SaltPassword(password, ResourceBlender_Express.Properties.Settings.Default["salt"].ToString());
        }
        /// <summary>
        /// Applies salt to a password.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <param name="salt">The salt to use.</param>
        /// <returns></returns>
        public static string SaltPassword(string password, string salt) {
            return MD5(salt + password);
        }

        /// <summary>
        /// Converts a .NET compatible country code to a Java compatible country code. These are mostly the same apart from a couple of unique cases.
        /// </summary>
        /// <param name="dotNetCulture">The dot net culture.</param>
        /// <returns></returns>
        public static string DotNetCulture2JavaCulture(string dotNetCulture) {
            string javaCulture = string.Empty;
            switch(dotNetCulture) {
                case "he-IL":
                    javaCulture = "iw_IL"; // bug as of Java v1.6.7
                    break;
                case "zh-Hans":
                    javaCulture = "zh_CN";
                    break;
                case "zh-Hant":
                    javaCulture = "zh_TW";
                    break;
                default:
                    javaCulture = dotNetCulture.Replace('-', '_');
                    break;
            }
            return javaCulture;
        }

        /// <summary>
        /// Converts a Java compatible country code to a .NET compatible country code. These are mostly the same apart from a couple of unique cases.
        /// </summary>
        /// <param name="dotNetCulture">The dot net culture.</param>
        /// <returns></returns>
        public static string JavaCulture2DotNetCulture(string javaCulture) {
            string dotNetCulture = string.Empty;
            switch(javaCulture) {
                case "iw_IL":
                    dotNetCulture = "he-IL"; // bug as of Java v1.6.7
                    break;
                default:
                    dotNetCulture = javaCulture.Replace('_', '-');
                    break;
            }
            return dotNetCulture;
        }

        /// <summary>
        /// For the specified string, replaces each Regex in pattern[], with it's equivalent index of replacements[]
        /// </summary>
        /// <param name="input">The input.</param>
        /// <param name="pattern">The pattern.</param>
        /// <param name="replacements">The replacements.</param>
        /// <returns></returns>
        public static string PregReplace(string stringToReplace, string[] pattern, string[] replacements, RegexOptions options) {
            if(replacements.Length != pattern.Length) {
                throw new ArgumentException("Pattern and replacement arrays must be equal length");
            }
            for(int i = 0; i < pattern.Length; i++) {
                stringToReplace = Regex.Replace(stringToReplace, pattern[i], replacements[i], options);
            }
            return stringToReplace;
        }

        /// <summary>
        /// Replaces the template tags in the specified template with their values from the source and destination arrays provided.
        /// </summary>
        /// <param name="template">The template.</param>
        /// <param name="pattern">The array of template tags.</param>
        /// <param name="replacements">The array of replacements.</param>
        /// <returns>A formatted template tag</returns>
        public static string FormatTemplateTags(string template, string[] pattern, string[] replacements) {
            if(replacements.Length != pattern.Length) {
                throw new ArgumentException("Pattern and replacement arrays must be equal length");
            }
            if(template.IndexOf("{=") < 0) {
                return template;
            } else {
                StringBuilder buffer = new StringBuilder(template);
                for(int i = 0; i < pattern.Length; i++) {
                    buffer.Replace(pattern[i], replacements[i]);
                }
                return buffer.ToString();
            }
        }

        /// <summary>
        /// Trims a string to the specified length, adding an ellipsis (...) if it is longer than the required length.
        /// </summary>
        /// <param name="text">The text to trim.</param>
        /// <param name="length">The maximum length of the final string.</param>
        /// <returns>A trimmed version of the text with an ellipsis if it was too long</returns>
        public static string TrimText(string text, int length) {
            if(text == null) {
                return "";
            }
            string snippet = Regex.Replace(text, @"<(.|\n)*?>", string.Empty); // strip HTML tags
            if(snippet.Length > length) {
                int stringLen = length - 3;
                if(stringLen < 0) {
                    stringLen = 0;
                }
                return snippet.Substring(0, stringLen).Trim() + "...";
            } else {
                return snippet;
            }
        }


        /// <summary>
        /// Gets the ISO 3166 code of the specified culture.
        /// </summary>
        /// <param name="info">The CultureInfo representing the culture.</param>
        /// <returns>An ISO3166 code to reresent a country which uses the CultureInfo specified</returns>
        public static string GetISO3166(CultureInfo info) {
            if(info.IsNeutralCulture) {
                if(info.TwoLetterISOLanguageName == "zh") { // chinese is awkward
                    return "cn";
                } else {
                    return new RegionInfo(CultureInfo.CreateSpecificCulture(info.Name).LCID).TwoLetterISORegionName;
                }
            } else {
                return new RegionInfo(info.LCID).TwoLetterISORegionName;
            }
        }

        /// <summary>
        /// Retrieves a Google translate code for a specified ISO 639-1 code. This is usually the same but sometimes varies. An empty string is returned if the code is unsupported by Google Translate.
        /// </summary>
        /// <param name="isoCode">The ISO 639-1 code.</param>
        /// <returns>The equivalent Google Translate language code, or an empty string if the code is unsupported by Google Translate.</returns>
        public static string ISOCode2GoogleCode(string isoCode) {
            string googleCode = string.Empty;
            switch(isoCode) { //some .NET peculiarities
                case "he":
                    googleCode = "iw";
                    break;
                case "pt":
                    googleCode = "pt-PT";
                    break;
                default: // only assign the code if the language is supported (null = hide the Google button)
                    Hashtable supportedLanguages = Constants.GoogleSupportedLanguages;
                    if(supportedLanguages.ContainsKey(isoCode)) {
                        googleCode = isoCode;
                    }
                    break;
            }
            return googleCode;
        }

        /// <summary>
        /// Parses source comments, references, flags and the msgctxt from PO entry meta data.
        /// Does NOT parse translator's comments or translation data.
        /// </summary>
        /// <param name="rawMetaData">The raw meta data.</param>
        /// <returns>An object containing the meta data</returns>
        public static POMetaData ParsePOMetaData(string rawMetaData) {
            POMetaData data = new POMetaData();
            if(rawMetaData != null && rawMetaData.Length > 0) {
                string[] lines = rawMetaData.Split('\n');
                foreach(string line in lines) {
                    if(line.Length < 4) {
                        continue;
                    }
                    switch(line.Substring(0, 2)) {
                        case "#.":
                            data.ExtractedComments.Add(line.Substring(2).Trim());
                            break;
                        case "#:":
                            data.References.Add(line.Substring(2).Trim());
                            break;
                        case "#,":
                            data.Flags.Add(line.Substring(2).Trim());
                            break;
                        default:
                            if(line.StartsWith("msgctxt")) {
                                if(line == "msgctxt \"\"") {
                                    data.Context = string.Empty;
                                } else if(line.Length >= 11) {
                                    data.Context = line.Substring(9, line.Length - 10);
                                }
                            }
                            break;
                    }
                }
            }
            return data;
        }

        /// <summary>
        /// Retrieves the date the assembly was compiled.
        /// </summary>
        /// <returns></returns>
        public static DateTime RetrieveLinkerTimestamp() {
            string filePath = System.Reflection.Assembly.GetCallingAssembly().Location;
            const int c_PeHeaderOffset = 60;
            const int c_LinkerTimestampOffset = 8;
            byte[] b = new byte[2048];
            System.IO.Stream s = null;
            try {
                s = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
                s.Read(b, 0, 2048);
            } finally {
                if(s != null) {
                    s.Close();
                }
            }
            int i = System.BitConverter.ToInt32(b, c_PeHeaderOffset);
            int secondsSince1970 = System.BitConverter.ToInt32(b, i + c_LinkerTimestampOffset);
            DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
            dt = dt.AddSeconds(secondsSince1970);
            dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
            return dt;
        }

        /// <summary>
        /// Compares entries in a dictionary by name.
        /// </summary>
        public class DictionaryValueComparer : IComparer<DictionaryEntry> {
            CaseInsensitiveComparer comp = new CaseInsensitiveComparer();
            public int Compare(DictionaryEntry x, DictionaryEntry y) {
                return comp.Compare(x.Value, y.Value);
            }
        }

        /// <summary>
        /// Compares language codes by their equivalent English names
        /// </summary>
        public class LanguageNameComparer : IComparer<string> {
            CaseInsensitiveComparer comp = new CaseInsensitiveComparer();
            public int Compare(string language, string otherLanguage) {
                string name1 = null, 
                       name2 = null;
                try {
                    name1 = CultureInfo.GetCultureInfo(language).EnglishName;
                } catch {
                    name1 = language;
                }
                try {
                    name2 = CultureInfo.GetCultureInfo(otherLanguage).EnglishName;
                } catch {
                    name2 = language;
                }
                return comp.Compare(name1, name2);
            }
        }

        /// <summary>
        /// Compares language codes by their equivalent English names
        /// </summary>
        public class LanguageNameDictionaryComparer : IComparer<DictionaryEntry> {
            private CompareMode mode;
            public enum CompareMode {
                Keys,
                Values
            }
            public LanguageNameDictionaryComparer(CompareMode mode)
                : base() {
                this.mode = mode;
            }

            CaseInsensitiveComparer comp = new CaseInsensitiveComparer();
            public int Compare(DictionaryEntry entry1, DictionaryEntry entry2) {
                string code1,
                       englishName1 = null,
                       code2,
                       englishName2 = null;
                if(mode == CompareMode.Keys) {
                    code1 = entry1.Key.ToString();
                    code2 = entry2.Key.ToString();
                } else {
                    code1 = entry1.Value.ToString();
                    code2 = entry2.Value.ToString();
                }
                try {
                    englishName1 = CultureInfo.GetCultureInfo(code1).EnglishName;
                } catch {
                    englishName1 = code1;
                }
                try {
                    englishName2 = CultureInfo.GetCultureInfo(code2).EnglishName;
                } catch {
                    englishName2 = code2;
                }
                return comp.Compare(englishName1, englishName2);
            }
        }

        /// <summary>
        /// Compares the equality of strings.
        /// </summary>
        public class StringEqualityComparer : IEqualityComparer<string> {
            public bool Equals(string x, string y) {
                return x.Equals(y, StringComparison.InvariantCultureIgnoreCase);
            }
            public int GetHashCode(string obj) {
                return obj.GetHashCode();
            }
        }
    }
}